/*
    Nios-sim - one simple NIOSII simulator only for personal interest and fun.
    Copyright (C) 2010  chysun2000@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>

#include "public.h"
#include "timer.h"
#include "niosii.h"


static struct timer_hw hw;
static uint32_t valid_mask[TIMER_REG_CNT] = {
	0x3,0xF,0xFFFF,0xFFFF,0xFFFF,0xFFFF
};

static uint32_t only_read_mask[TIMER_REG_CNT] = {
	0x2,0,0,0,0,0
};

static void update_period_reg(void);
static void timer_init(struct io_device * self)
{
	int32_t i = 0;
	
	self->priv_data = &hw;
	
	for (i=0;i<TIMER_REG_CNT;i++){
		hw.io_regs[i].addr = TIMER_BASE_ADDR + 4 * i;
		hw.io_regs[i].value = 0;
		hw.io_regs[i].valid_mask = valid_mask[i];
		hw.io_regs[i].only_read_mask = only_read_mask[i];
	}
	
	hw.set_period = 0;
	hw.curr_count = 0;

	printf("Timer Core at 0x%08X-0x%08X\n", hw.io_regs[0].addr, 
		   hw.io_regs[TIMER_REG_CNT-1].addr);
}

static int timer_is_belong(uint32_t address)
{
	int32_t ret_val = ADDR_IS_NOT_DEV;
	if ((address >= TIMER_BASE_ADDR) && (address < (TIMER_BASE_ADDR + TIMER_REG_CNT * 4))){
		ret_val = ADDR_IS_DEV;
	}

	return ret_val;
}
static void copy_snapshot(void);
static uint32_t timer_read(struct io_device * self, uint32_t addr, uint32_t data_len)
{
	uint32_t ret_val = 0;
	uint32_t index = 0;
	
	index = (addr - TIMER_BASE_ADDR) / 4;
	if (index >= 0 && index < TIMER_REG_CNT){
		if (index == TIM_REG_SNAPL || index == TIM_REG_SNAPH){
			copy_snapshot();
		}
		ret_val = hw.io_regs[index].value & hw.io_regs[index].valid_mask;
		ret_val = io_read_data(ret_val, data_len);
	}
	return ret_val;
}

static void timer_write(struct io_device * self, uint32_t addr, uint32_t data, uint32_t data_len)
{
	uint32_t index = 0;
	uint32_t temp = 0;
	uint32_t only_read_mask = 0;
	uint32_t valid_mask = 0;
	
	index = (addr - TIMER_BASE_ADDR) / 4;
	
	if (index >= 0 && index < TIMER_REG_CNT){
		temp = hw.io_regs[index].value;
		valid_mask = hw.io_regs[index].valid_mask;
		only_read_mask = hw.io_regs[index].only_read_mask;
		hw.io_regs[index].value = io_write_data_mask(temp, data, data_len, valid_mask, only_read_mask);
		
		
		if (index == TIM_REG_PERIODL){
			hw.set_period = hw.set_period &0xFFFF0000;
			hw.set_period = hw.set_period | (data & 0xFFFF);
			hw.curr_count = hw.set_period;
			update_period_reg();
		}
		else if (index == TIM_REG_PERIODH){
			hw.set_period = hw.set_period & 0xFFFF;
			hw.set_period = hw.set_period | ((data & 0xFFFF) <<16);
			hw.curr_count = hw.set_period;
			update_period_reg();
		}
		else if (index == TIM_REG_STATUS){
			if ((hw.io_regs[index].value & STATUS_TO_MASK) == 0){
				clean_ipending(self->irq_enable_mask);
			}
		}
	}
}

static int32_t timer_has_irq(struct io_device * self)
{
	int32_t ret_val = DEV_NO_IRQ;
	uint32_t ctrl_reg_val = 0;
	uint32_t status_reg_val = 0;
	
	ctrl_reg_val = hw.io_regs[TIM_REG_CTRL].value & hw.io_regs[TIM_REG_CTRL].valid_mask;
	status_reg_val = hw.io_regs[TIM_REG_STATUS].value & hw.io_regs[TIM_REG_STATUS].valid_mask;
	
	if ((ctrl_reg_val & CTRL_ITO_MASK) == CTRL_ITO_MASK){
		if ((status_reg_val & STATUS_TO_MASK) == STATUS_TO_MASK){
			ret_val = DEV_HAS_IRQ;
		}
	}
	return ret_val;
}

static void copy_snapshot(void)
{
	hw.io_regs[TIM_REG_SNAPL].value = hw.io_regs[TIM_REG_PERIODL].value;
	hw.io_regs[TIM_REG_SNAPH].value = hw.io_regs[TIM_REG_PERIODH].value;
}

static void update_period_reg(void)
{
	hw.io_regs[TIM_REG_PERIODL].value = hw.curr_count & 0xFFFF;
	hw.io_regs[TIM_REG_PERIODH].value = (hw.curr_count & 0xFFFF0000) >> 16;
	//copy_snapshot();
}

static void decrease_counter(void)
{
	if (hw.curr_count > 2){
		hw.curr_count -= 2;
	}
	else {
		hw.curr_count = 0;
	}
	update_period_reg();
}

static int32_t timer_can_decrease(void)
{
	int32_t ret = SIM_TRUE;
	uint32_t reg_ctrl_val = hw.io_regs[TIM_REG_CTRL].value;
	
	if ((reg_ctrl_val & CTRL_START_MASK) == 0){
		ret = SIM_FALSE;
		goto out;
	}
	
	if ((reg_ctrl_val & CTRL_STOP_MASK) != 0){
		ret = SIM_FALSE;
		goto out;
	}
	
	if (hw.set_period == hw.curr_count){
		if ((reg_ctrl_val & CTRL_CONT_MASK) == 0){
			ret = SIM_FALSE;
			goto out;
		}
	}
out:
	return ret;
}

static void update_status(void)
{
	if(hw.curr_count <= 0){
		hw.io_regs[TIM_REG_CTRL].value |= CTRL_ITO_MASK;
		hw.curr_count = hw.set_period;
		update_period_reg();
		hw.io_regs[TIM_REG_STATUS].value |= STATUS_TO_MASK;
	}
}

static void timer_simulate(struct io_device * self)
{
	if (timer_can_decrease() == SIM_TRUE) {
		decrease_counter();
		update_status();
	}
}

struct io_device timer_core = {
	.name = "timer_core",
	.init = timer_init,
	.is_belong = timer_is_belong,
	.read_data = timer_read,
	.write_data = timer_write,
	.has_irq = timer_has_irq,
	.simulate = timer_simulate,
	.irq_enable_mask = TIM_IRQ_MASK,
};


